package com.eiisan.security.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.eiisan.security.config.SecurityProperties;
import com.eiisan.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * @description: token工具类
 * @author: eii
 * @time: 2020/10/7 9:03 下午
 */
@Slf4j
@Component
public class TokenUtil {

    @Autowired
    private SecurityProperties properties;

    /**
     * 算法
     */
    private Algorithm algorithm;

    /**
     * 验证过期
     */
    private JWTVerifier verifier;

    /**
     * 不验证过期
     */
    private JWTVerifier expiredVerifier;

    /**
     * 签发者
     */
    private String issuer;

    /**
     * 是否过期，true为会过期
     */
    private boolean expired = false;

    /**
     * 过期时长(毫秒)
     */
    private Long expiration;

    /**
     * 声明
     */
    private String authorityClaim;

    /**
     * HttpHeader名称
     */
    private String header;

    /**
     * token前缀
     */
    private String tokenPrefix;

    /**
     * token前缀长度
     */
    private int tokenPrefixLength;

    @PostConstruct
    private void init() {
//        SecurityProperties properties = UContext.bean(SecurityProperties.class);
        algorithm = Algorithm.HMAC256(properties.getJwt().getSecret());
        issuer = properties.getJwt().getIssuer();
        expiration = properties.getJwt().getExpiration() * 1000;
        expired = expiration > 0;
        authorityClaim = properties.getJwt().getAuthorityClaim();
        header = properties.getJwt().getHeader();
        tokenPrefix = properties.getJwt().getTokenPrefix();
        tokenPrefixLength = tokenPrefix.length();
        verifier = JWT.require(algorithm).withIssuer(issuer).build();
        expiredVerifier = JWT.require(algorithm).acceptExpiresAt(0).withIssuer(issuer).build();
        Assert.notNull(algorithm, "algorithm must not be null");
        Assert.notNull(issuer, "issuer must not be null");
        Assert.notNull(expiration, "expiration must not be null");
        Assert.notNull(authorityClaim, "authorityClaim must not be null");
        Assert.notNull(header, "header must not be null");
        Assert.notNull(tokenPrefix, "tokenPrefix must not be null");
        Assert.notNull(verifier, "verifier must not be null");
        Assert.notNull(expiredVerifier, "expireAbleVerifier must not be null");
    }

    /**
     * 基本builder
     */
    private JWTCreator.Builder baseBuilder() {
        long now = System.currentTimeMillis();
        JWTCreator.Builder builder = JWT.create();
        builder.withIssuer(issuer);
        builder.withIssuedAt(new Date(now));
        if (expired) {
            builder.withExpiresAt(new Date(now + expiration));
        }
        return builder;
    }

    /**
     * 生成token
     */
    public String generate(String username, String authorities) {
        JWTCreator.Builder builder = baseBuilder();
        builder.withSubject(username);
        builder.withClaim(authorityClaim, authorities);
        return builder.sign(algorithm);
    }

    /**
     * 获取用户权限
     */
    public Authentication getAuthentication(String token){
        DecodedJWT jwt ;
        try {
            // 前面已经将exp置为合适的过期时间了,这里只需要判断其是否超过当前时间即可.
            jwt = expired ? expiredVerifier.verify(token) : verifier.verify(token);
        } catch (Exception e){
            log.info("Invalid JWT signature.");
            log.trace("Invalid JWT trace: {}", e);
            return null;
        }
        Collection<? extends GrantedAuthority> authorities = Arrays
                .stream(jwt.getClaim(authorityClaim).asString().split(","))// 获取用户权限字符串
                .map(SimpleGrantedAuthority::new).collect(Collectors.toList());// 将元素转换为GrantedAuthority接口集合
        User principal = new User(jwt.getSubject(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, "", authorities);
    }

    /**
     * 验证token
     */
    public Pair<String, String> verify(String token) {
        // 前面已经将exp置为合适的过期时间了,这里只需要判断其是否超过当前时间即可.
        DecodedJWT jwt = expired ? expiredVerifier.verify(token) : verifier.verify(token);
        String username = jwt.getSubject();
        String authorities = jwt.getClaim(authorityClaim).asString();
        if (authorities == null) {
            throw new IllegalArgumentException("illegal authorities:" + authorities);
        }
        return new Pair<>(username, authorities);
    }

    /**
     * 验证token,且一定考虑过期
     */
    public Pair<String, String> verifyWithNoExpired(String token) {
        DecodedJWT jwt = verifier.verify(token);
        String username = jwt.getSubject();
        String authorities = jwt.getClaim(authorityClaim).asString();
        if (authorities == null) {
            throw new IllegalArgumentException("illegal authorities:" + authorities);
        }
        return new Pair<>(username, authorities);
    }

    /**
     * 获取request中的token,如果为空或者前缀不符合设置,均返回null.
     */
    public String getRealToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (token == null || !token.startsWith(tokenPrefix)) {
            return null;
        }
        return token.substring(tokenPrefixLength);
    }

    /**
     * 获取request中的token,不做任何处理.
     */
    public String getFullToken(HttpServletRequest request) {
        return request.getHeader(header);
    }
}
